/*
 *  Copyright 2010-2011 Mark Eschbach
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 * $HeadURL: https://meschbach.com/svn/software/mee-utils/trunk/psi-jetty/src/main/java/com/meschbach/psi/jetty/ContextManager.java $
 * $Id: ContextManager.java 2793 2011-01-22 00:57:57Z meschbach $
 */
package com.meschbach.psi.jetty;

import com.meschbach.psi.PSIException;
import com.meschbach.psi.WebContainer;
import com.meschbach.psi.WebContext;
import com.meschbach.psi.util.FileUploader;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.mortbay.jetty.handler.AbstractHandler;

/**
 * A <code>CotnextManager</code> extends Jetty's <code>AbstractHandler</code>
 * to provide a RESTful style web application similar to Tomcat's manager
 * application, including deployment, starting and stopping conctext, and
 * undeployment.<p>
 *
 * This class is copyright 2010-2011 by Mark Eschbach and is licensed under the
 * Apache License, Version 2.0; accessible at
 * http://www.apache.org/licenses/LICENSE-2.0.<p>
 *
 * @author "Mark Eschbach" &lt;meschbach@gmail.com&gt;
 * @since 1.0.0
 * @version 2.1.2
 */
public class ContextManager extends AbstractHandler {

    public static final String REMOTE_WAR_PROPERTY = "PSI-war";
    protected static final String remoteDeployURI = "remote-deploy";
    protected String path;
    protected WebContainer wc;

    /**
     * Creates a <code>ContextManager</code> responding with the RESTful services
     * under the context <code>path</code>.
     * 
     * @param path is the path this is to respond too.
     * @param wc is the web container to interact with
     */
    public ContextManager(String path, WebContainer wc) {
        this.path = path;
        this.wc = wc;
    }

    public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException {
        try {
            /*
             * Ensure we are in our context path
             */
            if (!target.startsWith(path)) {
                return;
            }
            /*
             * Which operation have we requested?
             */
            String resource = target.substring(path.length());
            if (resource.startsWith(remoteDeployURI)) {
                /*
                 * Deploy uploaded WAR
                 */
                deployUploadedFile(resource, request, response);
            } else if (resource.equals("deploy-local")) {
                /*
                 * Deploy a server-local war
                 */
                deployLocalFile(resource, request, response);
            } else if (resource.equals("list")) {
                listDeployedContexts(request, response);
            } else if (resource.equals("shutdown")) {
                shutdownService(request, response);
            } else {
                /*
                 *
                 */
                //System.out.println("Unknown resource requested: '" + resource + "'");
            }
        } catch (Throwable t) {
            t.printStackTrace();
            /*
             * Set the status code to 500 failure
             */
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            response.setContentType("text/plain");
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Message", t.getLocalizedMessage());
            response.getWriter().println("An unexpected problem was encountered");
            t.printStackTrace(response.getWriter());
            response.flushBuffer();
        }
    }

    protected void listDeployedContexts(HttpServletRequest request, HttpServletResponse response) throws IOException {
        /*
         * Setup our response
         */
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType("text/plain");
        response.setCharacterEncoding("UTF-8");
        PrintWriter out = response.getWriter();
        /*
         * Print the contets
         */
        List<WebContext> ctx = wc.getContexts();
        for (WebContext c : ctx) {
            out.println(c.getName());
        }
        response.flushBuffer();
    }

    protected void shutdownService(HttpServletRequest request, HttpServletResponse response) throws IOException {
        /*
         * Shutdown our web container
         */
        Thread shutdownThread = new Thread(new Runnable() {

            public void run() {
                try {
                    Thread.sleep(100);
                    wc.shutdown();
                } catch (Throwable t) {
                    t.printStackTrace();
                }
            }
        });
        shutdownThread.start();
        /*
         * Notify the client we have recieved the request
         */
        response.setStatus(HttpServletResponse.SC_ACCEPTED);
        response.setContentType("text/plain");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().println("Shutting down");
        response.flushBuffer();
    }

    protected void deployUploadedFile(String resource, HttpServletRequest request, HttpServletResponse response) throws Throwable {
        /*
         * Grab the context path
         */
        if (resource.length() <= remoteDeployURI.length()) {
            throw new RuntimeException("Context path not given");
        }
        String contextPath = resource.substring(remoteDeployURI.length());
        //TODO MEE: I need a test to ensure that the string chomping occurs correctly, otherwise scary things happen
        //System.out.println("Remote deploy to context path '" + contextPath + "'");
        /*
         * Read in the input stream
         */
        final String POST_MESSAGE = "Body must contain a form POSTed WAR file";
        if (!ServletFileUpload.isMultipartContent(request)) {
            throw new RuntimeException(POST_MESSAGE);
        }
        /*
         * Prase the input stream
         */
        DiskFileItemFactory dfif = new DiskFileItemFactory();
        ServletFileUpload sfu = new ServletFileUpload(dfif);
        FileItemIterator fii = sfu.getItemIterator(request);
        /*
         * We require a single file
         */
        if (!fii.hasNext()) {
            throw new RuntimeException(POST_MESSAGE);
        }
        /*
         * Grab the file
         */
        FileItemStream fis = fii.next();
        String fieldName = fis.getFieldName();
        if (!fieldName.equals(REMOTE_WAR_PROPERTY)) {
            throw new PSIException("Expected field '" + REMOTE_WAR_PROPERTY + "', not '" + fieldName + "'");
        }
        /*
         * Store teh file to a temprorary file
         */
        FileUploader uploadedWar = new FileUploader(".war");
        uploadedWar.upload(contextPath, fis);
        /*
         * Ensure we don't have extra files
         */
        if (fii.hasNext()) {
            throw new RuntimeException("Only one file upload allowed at a time");
        }
        /*
         * Deploy the file
         */
        deployFile(uploadedWar.getResultingFile().getAbsolutePath(), contextPath, request, response, null);
    }

    protected void deployLocalFile(String resource, HttpServletRequest request, HttpServletResponse response) throws IOException {
        /*
         * Deploy our WAR
         */
        String contextPath = request.getHeader("Context-path");
        if (contextPath == null) {
            contextPath = request.getParameter("Context-path");
            if (contextPath == null) {
                throw new NullPointerException("Context-path header was not set on the request");
            }
        }
        String localWarFile = request.getHeader("Local-war-file");
        if (localWarFile == null) {
            localWarFile = request.getParameter("Local-war-file");
            if (localWarFile == null) {
                throw new NullPointerException("Local-war-file header was not set on the request");
            }
        }
        /*
         * Deploy the file using a reuable code
         */
        deployFile(localWarFile, contextPath, request, response);
    }

    protected void deployFile(String aLocalWARFile, String aContextName, HttpServletRequest request, HttpServletResponse response) throws IOException {
        deployFile(aLocalWARFile, aContextName, request, response, null);
    }

    protected void deployFile(String aLocalWARFile, String aContextName, HttpServletRequest request, HttpServletResponse response, Properties initProps) throws IOException {
        /*
         * Setup the repsonse for text processing
         */
        response.setContentType("text/plain");
        response.setCharacterEncoding("UTF-8");
        /*
         * Ensure teh local file exists
         */
        File localFile = new File(aLocalWARFile);
        if (!localFile.exists()) {
            /*
             * Notify the user the file doesn't exist
             */
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            response.getWriter().println("Couldn't find and/or access the given file");
        } else {
            /*
             * Issue accepted response
             */
            response.setStatus(HttpServletResponse.SC_ACCEPTED);
            response.setHeader("PSI-Context-path", aContextName);
            response.getWriter().println("Deploying conext '" + aContextName + "' from WAR '" + aLocalWARFile + "'");
            /*
             * Issue the deployment request
             */
            try {
                final WebContainer w = wc;
                synchronized (w) {
                    WebContext context = w.createContext(aContextName, aLocalWARFile);
                    context.start();
                }
            } catch (PSIException psie) {
                psie.printStackTrace();
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                psie.printStackTrace(response.getWriter());
                response.flushBuffer();
            }
        }
        response.flushBuffer();
    }
}
